﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Script.Serialization;
using BLToolkit.Aspects;
using BLToolkit.Data;
using BLToolkit.DataAccess;
using BLToolkit.Mapping;
using BLToolkit.Reflection;
using BLToolkit.Reflection.Extension;

namespace Devsoft.DB.Models.Flat
{
    [Serializable]
    public abstract class BaseModel:IModel
    {
        #region Model Operations

        public abstract void Save();

        public abstract void Delete();

        public abstract void Remove();

        public abstract void Insert(bool forceInsert = false);

        public abstract bool Update(bool forceUpdate = false);

        public abstract void InsertOrUpdate();

        public abstract void InsertOrReplace();

        #endregion

        #region Public Methods

        public bool HasPrimaryField()
        {
            return GetPrimaryFields().Count > 0;
        }

        public TResult GetPrimaryValue<TResult>()
        {
            if (PrimaryFields.Count != 1)
            {
                throw new Exception("No primary key or composite one defined ");
            }
            return (TResult)PrimaryFields[0].GetValue(this,new object[]{});

        }

        public override bool Equals(object obj)
        {
            if (obj != null && obj.GetType().Equals(this.GetType()))
            {
                if (PrimaryFields.Count > 0)
                {
                    return PrimaryFields.All(x => x.GetValue(obj, new object[] { }).Equals(x.GetValue(this, new object[] { })));
                }
            }
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            
            if (PrimaryFields.Count > 0)
            {
                int hashCode = 0;
                PrimaryFields.ForEach(x => hashCode = hashCode & x.GetValue(this, new object[] { }).GetHashCode());
                return hashCode;
            }
            return base.GetHashCode();
        }

        #endregion

        #region Properties

        [MapIgnore]
        [ScriptIgnore]
        public virtual bool IsNew
        {
            get
            {
                return CheckIfNewEntity();
            }
        }
        [ScriptIgnore]
        public List<PropertyInfo> PrimaryFields
        {
            get
            {
                return GetPrimaryFields();
            }
        }
        [ScriptIgnore]
        public PropertyInfo IdentityField
        {
            get
            {
                GetPrimaryFields();
                return _IdentityField;
            }
        }

        #endregion

        #region Protected Methods

        [NoInterception]
        protected List<PropertyInfo> GetPrimaryFields()
        {
            if (_PrimaryFields == null)
            {
                _PrimaryFields = this.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true).Any()).ToList();
                _IdentityField = this.GetType().GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof (IdentityAttribute), true).Any());
                /*
                                                                                                                                                 * 
                _PrimaryFields = new List<MemberAccessor>();
                Type type = this.GetType();
                TypeExtension typeExt = TypeExtension.GetTypeExtension(type, null);
                DbManager db = DataContextFactory.GetScopedDataContext<DbManager>();
                MappingSchema schema = db.MappingSchema;
                foreach (MemberMapper mm in schema.GetObjectMapper(type))
                {
                    if (mm.MapMemberInfo.SqlIgnore)
                        continue;
                    MemberAccessor ma = mm.MapMemberInfo.MemberAccessor;

                    if (TypeHelper.IsScalar(ma.Type))
                    {
                        bool isSet;
                        int order = schema.MetadataProvider.GetPrimaryKeyOrder(type, typeExt, ma, out isSet);

                        if (isSet)
                        {
                            _PrimaryFields.Add(ma);
                            if (ma.GetAttribute<IdentityAttribute>() != null)
                            {
                                if (_IdentityField != null) throw new Exception("Programming Error: Two or more identity fields defined");
                                _IdentityField = ma;
                            }
                        }
                    }
                }
                                                                                                                                                 * */

            }
            return _PrimaryFields;
        }

        protected void UpdateInsertedIdentity(object id)
        {
            PropertyInfo accessor = this.IdentityField;
            if (accessor != null)
            {
                accessor.SetValue(this, accessor.PropertyType == typeof(int) ? Convert.ToInt32(id) : Convert.ChangeType(id, accessor.PropertyType),
                                  new object[] {});
            }
        }
        
        protected virtual bool CheckIfNewEntity()
        {
            int count = PrimaryFields.Count;
            if (count == 1)
            {
                object val = PrimaryFields[0].GetValue(this, new object[] { });
                if (val == null || val.Equals(0))
                {
                    return true;
                }
            }
            else if (count > 1)
            {
                return PrimaryFields.All(accessor => accessor.GetValue(this, new object[] { }) == null);
            }

            // Insert if no primary keys defined else update
            return PrimaryFields.Count == 0;
        }

        #endregion

        #region Fields

        [NonSerialized]
        protected List<PropertyInfo> _PrimaryFields;
        [NonSerialized]
        protected PropertyInfo _IdentityField;
        [NonSerialized]
        protected bool _IsNew;

        #endregion
    }
}
